#include <gmpxx.h>
#include <iostream>
#include <pthread.h>



using namespace std;

#include "libs/bitarray.h"
#include "libs/types.h"



#define MAXTHREADNUM 8

//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

struct Arguments{
	BitArray* src;
	uint64 srcCompSize;
	BitArray* start;
	BitArray* end;
	mpz_t rank;
	uint32 threadNum;
	bool last;
};

void *findRank(void* arguments)
{
	Arguments* args = (Arguments*) arguments;
	mpz_init(args->rank);
	
	uint64 srcCompSize = args->srcCompSize;
	uint64 compSize;

	while(true)
	{

		compSize = args->start->getApproxCompressedSize();

		if( srcCompSize > compSize )
			mpz_add_ui(args->rank,args->rank,1);
		else if(srcCompSize == compSize)
			if(args->start < args->src)
				mpz_add_ui(args->rank,args->rank,1);
			
	
		(*(args->start))++;
		
		
		if(args->last)
		{
			if(args->start->isClear())
				break;
		}	
		else 
		{
			if( (*(args->start)) == (*(args->end)) )
				break;		
		}
		
	}
	
	//mpz_clear(args->rank);
}

int main(int argc, char  **  argv)
{

	BitArray src(argv[1]);
	cout << "source read " << endl;
	
	uint32 bitsToSet = 0;
	uint32 index = MAXTHREADNUM-1;
	while(index)
	{
		++bitsToSet;
		index >>= 1;
	}

	uint32 THREADNUM = 1 << bitsToSet;
	Arguments args[THREADNUM];
	pthread_t threads[THREADNUM-1];
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
	cout << THREADNUM << " threads available" << endl;
	
	uint32 bitIdx = 0;
	
	uint64 srcCompSize = src.getApproxCompressedSize();
	
	for(int32 i = 0; i < THREADNUM; i++)
	{
	
		args[i].src = &src;
		args[i].srcCompSize = srcCompSize;
		args[i].start = new BitArray(src.getByteSize(),false);
		args[i].threadNum = i;
		
		if(i != (THREADNUM-1) )
			args[i].end = new BitArray(src.getByteSize(),false);
	

		
		for(int32 j = 0; j < bitsToSet; j++)
			args[i].start->setBit((bitIdx >> j) & 1,src.getSize()-bitsToSet+j);
			
		
	
		bitIdx++;	

		if(i == (THREADNUM-1) )
		{
			args[i].last = true;
			findRank((void*) &args[i]);
		}
		else
		{
			args[i].last = false;
			for(int32 j = 0; j < bitsToSet; j++)
				args[i].end->setBit((bitIdx >> j) & 1,src.getSize()-bitsToSet+j);
			
			pthread_create(&threads[i],&attr,findRank,(void*) &args[i]);
			
		}
	
	}
	pthread_attr_destroy(&attr);

	for(unsigned t= 0; t < THREADNUM-1; t++)
	{
		pthread_join(threads[t],NULL);
	}
	cout << "threads joined " << endl;

	
	
	mpz_t rank;
	mpz_init(rank);
	
	for(unsigned t= 0; t < THREADNUM; t++)
	{
		mpz_add (rank, rank, args[t].rank);
		mpz_clear(args[t].rank);
	}

	cout << "rank: ";
	gmp_printf("%Zd\n",rank);
	
	
	for(int32 i = 0; i < THREADNUM; i++)
	{
		delete args[i].start;
		if(i != THREADNUM-1)
			delete  args[i].end;
	}


	mpz_t newRank;
	mpz_init(newRank);
	mpz_add_ui(newRank,newRank,2);
	mpz_pow_ui (newRank, newRank, src.getSize());
	mpz_sub_ui(newRank,newRank,1);
	mpz_sub(newRank,newRank,rank);
	
	
	cout << "new rank calculated: ";
	gmp_printf("%Zd\n",newRank);

	
	BitArray test(src.getByteSize(),false);

	
	
	while(true)
	{

		pthread_attr_init(&attr);
		pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE);
		
		bitIdx = 0;
		
		srcCompSize = test.getApproxCompressedSize();
		
		for(int32 i = 0; i < THREADNUM; i++)
		{
		
				args[i].src = &test;
				args[i].srcCompSize = srcCompSize;
				args[i].start = new BitArray(test.getByteSize(),false);
				
				if(i != (THREADNUM-1) )
					args[i].end = new BitArray(test.getByteSize(),false);
			
				for(int32 j = 0; j < bitsToSet; j++)
					args[i].start->setBit((bitIdx >> j) & 1,test.getSize()-bitsToSet+j);
			
				bitIdx++;	

				if(i == (THREADNUM-1) )
				{
					args[i].last = true;
					findRank((void*) &args[i]);
				}
				else
				{
					args[i].last = false;
					for(int32 j = 0; j < bitsToSet; j++)
						args[i].end->setBit((bitIdx >> j) & 1,test.getSize()-bitsToSet+j);
					
					pthread_create(&threads[i],&attr,findRank,(void*) &args[i]);
					
				}
			
			}
			pthread_attr_destroy(&attr);

			for(unsigned t= 0; t < THREADNUM-1; t++)
			{
				pthread_join(threads[t],NULL);
			}
			
		
			mpz_set_ui(rank,0);
			
			for(unsigned t= 0; t < THREADNUM; t++)
				mpz_add (rank, rank, args[t].rank);

			
			
			for(int32 i = 0; i < THREADNUM; i++)
			{
				delete args[i].start;
				if(i != THREADNUM-1)
					delete  args[i].end;
			}
	
	
		if ( mpz_cmp(rank,newRank) == 0 )
			break;
		else
			test++;
		
	}
	
	mpz_clear(rank);
	mpz_clear(newRank);
	
	cout << endl <<  "replacement found ";
	
	
	test.write(argv[2]);
	
}
